/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    pool.cpp  
  Description: 

*/

#include "stdafx.h"

// PBP must be a multiple of memory allocation alignment
#define POOL_BUCKET_PRECISION (1 << 5)
#define POOL_BUCKET_MASK (~(POOL_BUCKET_PRECISION-1))
#define POOL_BUCKET_MAXSIZE (1 << 20)
#define MAX_CLEAN_ATTEMPTS 4

// Bucket index defined as PBP * (i + 1) = (sz & PBM) * (i + 1)
// i = (sz & PBM) / PBP

namespace m
{
	namespace alloc
	{
		_pool::_pool()
			: _CountBuckets(POOL_BUCKET_MAXSIZE / POOL_BUCKET_PRECISION),
			_Buckets(nullptr),
			UnusedPoolSize(0),
			UsedPoolSize(0)
		{
			_Buckets = new _pool_bucket*[_CountBuckets];
			for (size_t i = 0; i < _CountBuckets; ++i)
			{
				_Buckets[i] = new _pool_bucket(POOL_BUCKET_PRECISION * (i + 1));
			}
		}

		_pool::~_pool()
		{
			for (size_t i = 0; i < _CountBuckets; ++i)
			{
				delete _Buckets[i];
			}

			delete[] _Buckets;
		}

		void * _pool::get(const size_t& sz)
		{
			size_t idx = (sz & POOL_BUCKET_MASK) / POOL_BUCKET_PRECISION;
			assert(idx < _CountBuckets && sz <= _Buckets[idx]->MaxSize);
			if (idx < _CountBuckets && sz <= _Buckets[idx]->MaxSize)
			{
				return _Buckets[idx]->get(this);
			}
			throw std::bad_alloc();
		}

		void _pool::put(void * data)
		{
			// Bucket pointer is part of the node. This just tells the put method at bucket 0 to put the node back
			// to the correct bucket.
			_pool_bucket::put(this, data);
		}

		size_t _pool::clear(const size_t & ClearSpace)
		{
			size_t Cleaned = 0;
			size_t ToClean = ClearSpace / _CountBuckets;
			size_t CleanAttempts = 0;

			// Amortize clean space over each node and break if either we didn't clean the minimum amount
			// or we exceeded the number of attempts.

			while (CleanAttempts < MAX_CLEAN_ATTEMPTS && Cleaned <= ClearSpace)
			{
				size_t CleanedThisLoop = 0;

				for (size_t i = 0; i < _CountBuckets; ++i)
				{
					Cleaned += _Buckets[i]->clear(this, ToClean);
				}

				if (CleanedThisLoop < ToClean)
				{
					break;
				}

				++CleanAttempts;
			}

			return Cleaned;
		}
	}
}